﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Perst;

namespace Universe.Cache
{
    public class CachePerstStorage : ICacheStorage
    {
        public const int PrefixLength = 1000;
        object SyncInit = new object();
        private CachePerstConnection _con;
        private MyRootClass Root;
        private Storage Db;

        public CachePerstStorage(CachePerstConnection _con)
        {
            this._con = _con;
        }

        private MyRootClass Init()
        {
/*
            lock(SyncInit)
            {
                if (Root == null || Db == null)
                {
*/
/*
                    if (!File.Exists(_con.FileName))
                        File.WriteAllBytes(_con.FileName, new byte[0]);
*/

                    
            lock(SyncInit)
            {
                if (Db == null)
                {
                    Db = StorageFactory.Instance.CreateStorage();
                    Db.SetProperty("perst.multiclient.support", true);
                }
            }
            
                    Db.Open(_con.FileName);
                    Db.BeginSerializableTransaction();
                    MyRootClass root = (MyRootClass)Db.Root;
                    if (root == null)
                    {
                        root = new MyRootClass(Db);
                        Db.Root = root;
                    }

                    Root = root;
/*
                }
            }
*/

            return Root;
        }

        Dictionary<string, Guid> _Keys = new Dictionary<string, Guid>(StringComparer.InvariantCultureIgnoreCase);
        private DateTime _KeysExpiration = DateTime.MinValue;

        private Dictionary<string, Guid> GetKeys()
        {
            lock (_Keys)
            {
                var now = DateTime.Now;
                if (now >= _KeysExpiration)
                {
                    _KeysExpiration = now.AddHours(1);
                    _Keys = new Dictionary<string, Guid>(StringComparer.InvariantCultureIgnoreCase);
                }
            }

            return _Keys;
        }

        private Guid GetKeyId(string key)
        {
            var keys = GetKeys();

            // In memory?
            Guid keyId;
            if (keys.TryGetValue(key, out keyId))
            {
                return keyId;
            }

            // already in DB?
            string prefix =
                key.Length > PrefixLength
                    ? key.Substring(0, PrefixLength)
                    : key;

            string suffix =
                key.Length > PrefixLength
                    ? key.Substring(PrefixLength)
                    : null;

            KeyPersistent[] similar = Root.PrefixIndexOfKey.Get(prefix, prefix);
            foreach (KeyPersistent candidate in similar)
            {
                if (suffix == candidate.Suffix)
                {
                    keyId = candidate.Id;
                    keys[key] = keyId;
                    return keyId;
                }
            }

            // new key :(
            keyId = Guid.NewGuid();
            KeyPersistent kp = new KeyPersistent() {Id = keyId, Prefix = prefix, Suffix = suffix};
            Root.PrefixIndexOfKey.Put(kp);
            keys[key] = keyId;

            return kp.Id;
        }

        public bool Get(string key, byte[] stamp, out byte[] result)
        {
            MyRootClass root = null;
            try
            {
                root = Init();

                EntryPersistent entity = null;
                try
                {
                    Guid keyId = GetKeyId(key);
                    entity = Root.KeyIndexOfEntity.Get(keyId);
                    Db.CommitSerializableTransaction();
                }
                catch (Exception)
                {
                    Db.RollbackSerializableTransaction();
                }

                if (entity == null)
                {
                    result = null;
                    return false;
                }
                else
                {
                    bool isSame = Utils.Equals(stamp, entity.Stamp);
                    result = isSame ? entity.Value : null;
                    return isSame;
                }
            }
            finally
            {
                if (root != null)
                    root.Storage.Close();
            }

        }

        public void Put(string key, byte[] stamp, DateTime expired, byte[] value)
        {
            MyRootClass root = null;
            
            try
            {
                root = Init();
                try
                {
                    Guid keyId = GetKeyId(key);
                    var @new = new EntryPersistent() {Expired = expired, Key = keyId, Stamp = stamp, Value = value};
                    var prev = Root.KeyIndexOfEntity.Get(keyId);
                    if (prev != null)
                    {
                        Root.KeyIndexOfEntity.Remove(prev);
                        Root.ExpiredIndexOfEntity.Remove(prev);
                        prev.Deallocate();
                    }

                    Root.KeyIndexOfEntity.Put(@new);
                    Root.ExpiredIndexOfEntity.Put(@new);

                    Db.CommitSerializableTransaction();
                }
                catch(Exception ex)
                {
                    Db.RollbackSerializableTransaction();
                    throw;
                }
            }
            finally
            {
                if (root != null)
                    Db.Close();

            }
        }

        public void FlushExpiration()
        {
        }

        public void Clean(int maxMegabytes)
        {
        }


        private class MyRootClass : Persistent
        {
            public FieldIndex<Guid, EntryPersistent> KeyIndexOfEntity;
            public FieldIndex<DateTime, EntryPersistent> ExpiredIndexOfEntity;
            public FieldIndex<string, KeyPersistent> PrefixIndexOfKey;

            public MyRootClass(Storage db)
                : base(db)
            {
                KeyIndexOfEntity = db.CreateFieldIndex<Guid, EntryPersistent>("Key", true);
                ExpiredIndexOfEntity = db.CreateFieldIndex<DateTime, EntryPersistent>("Expired", false);
                PrefixIndexOfKey = db.CreateFieldIndex<string, KeyPersistent>("Prefix", false);
            }

            // Default constructor is needed for Perst to be able to 
            // instantiate instances of loaded objects 
            public MyRootClass()
            {
            }
        }

        public class EntryPersistent : Persistent
        {
            public Guid Key;
            public DateTime Expired;
            public byte[] Stamp;
            public byte[] Value;
        }

        public class KeyPersistent : Persistent
        {
            public Guid Id;
            public string Prefix;
            public string Suffix;
        }
    }
}